/**
	DIMGA
	Distributed Island Model Genetic Algorithm 

	Copyright (c) 2006 Nikola Sivacki nsivacki@gmail.com

	This program is free software; you can redistribute it and/or
	modify in under the terms of the GNU General Public Licence as
	published by the Free Software Foundation; either version 2 of
	the Licence, or [at your option] any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAT PURPOSE. See the
	GNU General Public Licence for more details.

	You should have received a copy of the GNU General Public Licence
	along with this program; if not, write to the Free Software 
	Foundation Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <unistd.h>
#include <dlfcn.h>
#include <errno.h>

#include <iostream>
#include <list>
#include <string>
#include <fstream>
#include <pthread.h>
#include <math.h>

//#include "Evaluator.h"
#include "gaevaluator.h"

#include "config.h"
#include "tcp/netutil.hpp"
#include "genotype.h"
#include "vivarium.h"
#include "util/trace.h"
#include "util/kb.h"

using namespace std;

#define MUTATION_AMOUNT     1

#define GENERATIONS         100

#define MAX_NODES 50
#define MAX_PORTS 50

#define NUM_IN              4
#define NUM_OUT             4

#define BUFSIZE             1000
#define MAXTESTGENSIZE      500000

static const float MAXFITNESS = 999999.0f;

static int lenbuf;

static char *rbuf;
static char *sbuf;

static int outaddr[4]  =   {0,0,0,0};
static int outport[4]  =   {0,0,0,0};
static int outsck[4]   =   {0,0,0,0};

static int inport[4]   =   {0,0,0,0};
static int insck[4]    =   {0,0,0,0};

static struct sockaddr_in  insaddr[MAX_PORTS];
static struct sockaddr_in  outsaddr[MAX_NODES];

static int nports = 0;
static int nnodes = 0;

static int csock       = 0;
static int cport       = -1;
static struct sockaddr_in csaddr;

static string sEvalModule = "";

Vivarium* viv = NULL;
GAEvaluator *nev = NULL;
pthread_mutex_t mtx;

void print_usage()
{
    printf("Usage:\n");
    printf("dimga [-p] [-c config_file] [-f vivarium_file] [-t]\n");
    printf("-e    - Eval module file (.so file) containing the dimga_get_evaluator function\n");
    printf("-p    - Run in local mode, no config file\n");
    printf("-c    - Use config file\n");
    printf("-f    - Continue the search from stored file\n");
    printf("-t    - Execute test functions in module\n");
    exit(0);
}

void print_menu()
{
  printf("s    -    Save vivarium\n");
  printf("m    -    Set Mutation Ratio\n");
  printf("c    -    Continue\n");
  printf("q    -    Exit\n");
}

// Load the object containing the evaluator from the dynamic library
void loadEvalModule(string& evalmodule)
{
  log("Loading file:%s", evalmodule.c_str());
  void* module = NULL;
  dlerror();
  
  module = dlopen(evalmodule.c_str(), RTLD_NOW);
  const char* dlerr = dlerror();

  if (dlerr)
  {
    log("Error:%s\n", dlerr);
    exit (1);
  }

  dlerror();

  GAEvaluator* (*get_eval)() = (GAEvaluator* (*)()) dlsym(module, "dimga_get_evaluator");
  const char* dlerr2 = dlerror();

  if (dlerr2)
  {
    log("Symbol error:%s", dlerr2);
    exit (1);
  }
  nev = get_eval();
}

// Called after data is received
void process_in(char* data)
{
  long f= viv->GetPopSize();
  int iMigrants = (int)((double)(viv->GetPopSize()) * nev->getMigrationRatio());
  long iMigrant = rand() % viv->GetPopSize();

  pthread_mutex_lock(&mtx);
  memcpy(viv->GetGenome(iMigrant), rbuf, lenbuf);
  pthread_mutex_unlock(&mtx);
}

// Called prior to sending data
void process_out(char* data)
{
  long f= viv->GetPopSize();
  int iMigrants = (int)((double)(viv->GetPopSize()) * nev->getMigrationRatio());
  long iMigrant = rand() % viv->GetPopSize();

  pthread_mutex_lock(&mtx);
  memcpy(sbuf, viv->GetGenome(iMigrant), lenbuf);
  pthread_mutex_unlock(&mtx);
}

// Configuration...
void configure(string _sFile)
{
  cfg_open(_sFile.c_str());

  log("Reading config");

  cport = cfg_getint("configport", -1);
  log("config: configport:%d", cport);

  sEvalModule = string(cfg_getstr("evalmodule", ""));
  log("config: eval module:%s", sEvalModule.c_str());

  nnodes=0;
  nports=0;

  while(true)
  {
    char nodename[256];

    sprintf(nodename,"node[%d]", nnodes);
    char* addr=cfg_getstr(nodename, "");

    // exit if the node name does not exist
    if (strlen(addr)==0)
    {
      break;
    }

    if (nnodes > MAX_NODES)
    {
      log("CONFIG ERROR: Maximum node number reached (%d)", MAX_NODES);
      break;
    }

    char* sep=strchr(addr,':');

    if (sep==NULL)
    {
      log("CONFIG ERROR: port missing in address %s", addr);
      break;
    }

    outport[nnodes]=atoi(sep+1);
    *sep=0;
    outaddr[nnodes]=getaddr(addr);

    log("config: nodename:%s, addr:%s, port:%d", nodename, addr, outport[nnodes]);
    nnodes++;
  }

  log("Found %d nodes", nnodes);

  int nports=0;

  while(true)
  {
    char portvar[256];
    sprintf(portvar,"inport[%d]", nports);
    int inp = cfg_getint(portvar, -1);

    if (inp==-1)
    {
      break;
    }

    if (nports > MAX_PORTS)
    {
      log("ERROR: Maximum input ports reached (%d)", MAX_PORTS);
      break;
    }

    inport[nports]=inp;

    log("config: %s:%d", portvar, inp);
    nports++;
  }

  log("Found %d ports", nports);

  for(int i = 0 ; i < nports; i++)  
  {
    insck[i]=socket(AF_INET, SOCK_DGRAM, 0);
    if (insck[i] < 0)
    {
      log("socket creation failed %s", strerror(errno));
    }

    insaddr[i].sin_family = AF_INET;
    insaddr[i].sin_port = htons(inport[i]);
    insaddr[i].sin_addr.s_addr = htonl(INADDR_ANY);

    int ret = bind(insck[i], (sockaddr*)&insaddr[i], sizeof(insaddr[i]));
    
    if (ret < 0)
    {
      log("Binding failed %s", strerror(errno));
    }
  }

    
  for(int i = 0 ; i < nnodes; i++)  
  {
    outsck[i]=socket(AF_INET, SOCK_DGRAM, 0);
    if (outsck[i] < 0)
    {
        log("socket creation failed %s", strerror(errno));
    }

    outsaddr[i].sin_family = AF_INET;
    outsaddr[i].sin_port = htons(outport[i]);
    outsaddr[i].sin_addr.s_addr = htonl(outaddr[i]);
  }

  cfg_close();
}

bool starts_with(char* str, const char *start)
{
  return (strstr(str, start) == str);
}

char* getVal(char* command)
{
  char* ws = strchr(command,' ');
  if (ws!=NULL)
  {
    return ws+1;
  }
  return NULL;
}

void runControl(char* request, char* response)
{
  if (starts_with(request, "debug.exit"))
  {
    exit(1);
  } else if (starts_with(request, "mr.set"))
  {
    char* val = getVal(request);
    float fval = atof(val);
    viv->SetMutationRatio(fval);
  } else if (starts_with(request,"mr.get"))
  {
    float fmr = viv->GetMutationRatio();
    sprintf(response, "%2.4f\n", fmr);
    return;
  } else if (starts_with(request, "get.fitness"))
  {
    float fBestFitness = nev->Evaluate(viv->GetBest()->GetBytes(), viv->GetGenSize());
    fBestFitness = fBestFitness * fBestFitness;
    sprintf(response,"%2.8f\n", fBestFitness);
    return;
  } else if (starts_with(request, "get.timefitness"))
  {
    float fBestFitness = nev->Evaluate(viv->GetBest()->GetBytes(), viv->GetGenSize());
    fBestFitness = fBestFitness * fBestFitness;
    sprintf(response,"%ld %2.8f\n", time(NULL), fBestFitness);
    return;
  } else if (starts_with(request, "population.randomise"))
  {
    pthread_mutex_lock(&mtx);
    viv->Randomize();
    pthread_mutex_unlock(&mtx);
  } else if (starts_with(request,"population.save"))
  {
    char* filename = getVal(request);
    viv->SaveVivarium(filename);
  } else if (starts_with(request,"result.save"))
  {
    char* filename = getVal(request);
    nev->toFile(viv->GetBest()->GetBytes(), viv->GetGenSize(), filename);
  } else if (starts_with(request, "result.get"))
  {
    char* filename = getVal(request);
    FILE* f = fopen(filename, "r");
    if (f==NULL)
    {
      sprintf(response,"ERROR");
      return;
    }
    int cnt=0;
    int ch=fgetc(f);

    while(ch!=EOF)
    {
      response[cnt++]=ch;
      ch = fgetc(f);
    };
    fclose(f);
    return;
  }

  sprintf(response, "OK\n");
}

void* controlWorker(void* param)
{
  log("Control Worker started on port %d", cport);
  char buf[BUFSIZE];
  char resp[BUFSIZE];

  if (listen(csock, 100) < 0)
  {
    log("controlWorker: listen failed: %s", strerror(errno));
    pthread_exit(NULL);
  }

  while(true)
  {
    struct sockaddr_in clientaddr;
    socklen_t addrlen = sizeof(sockaddr_in);

    int clientsocket = accept(csock, (sockaddr*) &clientaddr, &addrlen);

    if (clientsocket < 0)
    {
      log("Error accepting client socket: %s", strerror(errno));
      sleep(1);
      continue;
    }

    memset(buf, 0, BUFSIZE);
    size_t sz = recv(clientsocket, buf, BUFSIZE, 0);

    if (sz < 0)
    {
      log("Error reading from control socket: %s", strerror(errno));
      continue;
    }

    log("Received (%ld bytes): %s", sz, buf);

    runControl(buf, resp);

    send(clientsocket, resp, strlen(resp)+1, 0);

    close(clientsocket);

  }

  pthread_exit(NULL);
}

void initControl()
{
  csock=socket(PF_INET, SOCK_STREAM, 0);

  csaddr.sin_family = AF_INET;
  csaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  csaddr.sin_port = htons(cport);

  if (csock < 0)
  {
      log("control socket creation failed %s", strerror(errno));
      return;
  }

  int ret = bind(csock, (struct sockaddr*)&csaddr, sizeof(csaddr));
  
  if (ret < 0)
  {
    log("Control socket bind failed %s", strerror(errno));
    return;
  }

  pthread_t t1;
  pthread_create(&t1, NULL, controlWorker, NULL);
}


// The server function, receiving data
void* server(void* param)
{
  log("Server thread started");
  fd_set fd;
  
  int dsize = getdtablesize();     

  timeval tv;
  tv.tv_sec = 1;
  tv.tv_usec = 0;

  int ret; 
  int which = -1;
    
  while(1)
  {
    FD_ZERO(&fd);
    
    for(int i = 0 ; i < NUM_IN; i++)
    {
      FD_SET(insck[i], &fd);
    }   
    
    ret = select(outsck[NUM_OUT-1]+1, &fd, (fd_set * )NULL, (fd_set *) NULL, NULL);
    
    if (ret < 0)
    {
      log("Error in select %s", strerror(errno));
      exit(0);
    }

//    printf("number of sockets pending : %d", ret);
    which = -1;
    for(int i = 0 ; i < NUM_IN; i++)
    {
      if (FD_ISSET(insck[i], &fd))
      { 
        which = i;
//            printf("s:select exited on incoming on port: %d,\n", inport[which]);
        socklen_t fromsize = sizeof(insaddr[which]); 
    
        int iPackets = (lenbuf-1)/BUFSIZE + 1;

        for(int p = 0 ; p < iPackets; p++)
        {
          ret = recvfrom(insck[which], rbuf + BUFSIZE*p, BUFSIZE, 0, (sockaddr*)&insaddr[which], &fromsize);
          if (ret < 0)
          {
            log("Error in server recvfrom %s", strerror(errno));
            continue;
          }
        }
//            printf("s:received :%s for %d, from %d\n", rbuf, inport[which], ntohs(insaddr[which].sin_port));
        process_in(rbuf);
      }
    }
        
    if (which == -1)
    {
      continue;
    }
  }
}

bool checkShellCommand()
{
  int ch = 0;

  if ((ch = kbhit()) != 0)
  {
    char comm[100];
    ch = tolower(ch);

    if (ch != 0)
    {
      while ((ch != 'c') && (ch != 'q'))
      {
        print_menu();

        fscanf(stdin, "%s", comm);
        ch = comm[0];
        ch = tolower(ch);

        switch(ch){
          case 'q': return (true); break; 
          case 's': 
            static char fullName[500];
            strcpy(fullName, strchr(comm,' ') + 1);
            strcat(fullName, ".population");
            viv->SaveVivarium(fullName);
            strcpy(fullName, strchr(comm,' ') + 1); 
            strcat(fullName, ".best");
            nev->toFile(viv->GetBest()->GetBytes(), viv->GetGenSize(), fullName);
            break;
          case 'm': 
            float fmr = atof(strchr(comm, ' ') + 1);
            viv->SetMutationRatio(fmr);
            break;
        }
      }
    }
  }

  return (false);
}

void checkTestTrigger(string& sTestGen)
{
  if (sTestGen.size() > 0)
  {
    char* gen = new char[MAXTESTGENSIZE];
    FILE *f = fopen(sTestGen.c_str(), "r");

    if (f == NULL) 
    {
      log("Cannot open test file, exiting...");
      exit (1);
    }

    long rd = fread(gen, 1, MAXTESTGENSIZE, f);

    nev->Test((unsigned char*) gen, -1);

    delete gen;
    exit(0);
  }
}

void initBuffers()
{
  lenbuf = nev->getSize();
  int lenbuf_alloc = lenbuf;

  if(lenbuf < BUFSIZE)
  {
    lenbuf_alloc = BUFSIZE;
  }

  rbuf = new char[lenbuf_alloc];
  sbuf = new char[lenbuf_alloc];
}

int main(int argc, char** argv)
{
  initlog();

  string sStoredVivarium = "";
  string sTestGen = "";
  extern char* optarg;
  extern int optind, optopt;

  char* cfile = NULL;
  int c;
  string sConfigFile = "";

  bool localmode = false;

  if ((argc > 1) && (strcmp(argv[1], "--help") ==0))
  {
    print_usage();
  }

  while ((c = getopt(argc, argv, "c:f:pt:e:m:")) != -1)
  {
    switch(c)
    {
      case 'p': 
        localmode = true;
        break;
      case 'c': 
        cfile = optarg;
        sConfigFile = string(optarg); 
        break;
      case 'f': 
        sStoredVivarium = string(optarg);
        break;
      case 't': 
        sTestGen = string(optarg);
        break;
      case 'm':
        cport = atoi(optarg);
        break;
      case 'e':
        sEvalModule = string(optarg);
        break;
      case '?': 
        error("Invalid argument");
        print_usage();
    }
  }
 
  if (sConfigFile=="")
  {
    error("Config file missing");
    print_usage();
    exit(1);
  }

  configure(sConfigFile);

  if (cport==-1)
  {
    error("Invalid config port");
    print_usage();
    exit(1);
  }
  

  if (sEvalModule=="")
  {
    print_usage();
    exit(1);
  }

  log("Loading eval module");

  loadEvalModule(sEvalModule);

  initBuffers();

  initControl();

  // check for test functionality
  checkTestTrigger(sTestGen);

  if (sStoredVivarium!="")
  {
    log("loading vivarium %s...", sStoredVivarium.c_str());
    viv = new Vivarium(sStoredVivarium.c_str(), 
                       nev, 
                       nev->getMutationRatio(), 
                       nev->getMutationAmount());
  } else
  {
    log("new vivarium %d, %d, %f %d", 
                        nev->getPop(),
                        nev->getSize(),
                        nev->getMutationRatio(),
                        nev->getMutationAmount());

    viv = new Vivarium(nev->getPop(),
                       nev->getSize(),
                       nev,
                       nev->getMutationRatio(),
                       nev->getMutationAmount());
  }
 

  // Initialize the server thread
  if (!localmode)
  {
    pthread_mutex_init(&mtx, NULL);

    pthread_t t1;
    pthread_create(&t1, NULL, server, NULL);
  } else
  {
    log("Server mode");
  }
  
  int fromsize = sizeof(outsaddr[0]);

  float fTargetFitness = nev->getTarget();
  float fBestFitness =  MAXFITNESS;

  int ex=0;
  int ret = 0;
  
  while((fBestFitness > fTargetFitness) && (!ex))
  {
    if (!localmode)
    {
      pthread_mutex_lock(&mtx);
    }

    for(long i = 0 ; i < GENERATIONS; i++)
    {
      viv->Evolve();
    }

    fBestFitness = nev->Evaluate(viv->GetBest()->GetBytes(), viv->GetGenSize());
    fBestFitness = fBestFitness * fBestFitness;

    //printf("%f ", fBestFitness);
    //nev->print(viv->GetBest()->GetBytes(), viv->GetGenSize());
    //printf("\n");

    if (!localmode)
    {
      pthread_mutex_unlock(&mtx);
      for(int i = 0 ; i < NUM_OUT; i++)
      {
        process_out(sbuf);
        int iPackets = (lenbuf-1)/BUFSIZE + 1;

        for(int p = 0 ; p < iPackets; p++)
        {
          ret = sendto(outsck[i],
                       sbuf + p*BUFSIZE,
                       BUFSIZE,
                       0,
                       (sockaddr*)&outsaddr[i],
                       sizeof(outsaddr[i]));

          if (ret < 0)
          {
            log("Error in c:sendto %s", strerror(errno));
            continue;
          }
        }
//        printf("c:sent:%s to %d\n", rbuf, outport[i]);
      }
    } // ..if
  } // ..while

  log("Target reached, best fitness: %2.4f, target fitness:%2.4f",fBestFitness, fTargetFitness);
  viv->SaveVivarium("target.population");
  nev->toFile(viv->GetBest()->GetBytes(), viv->GetGenSize(), "target.best");
  
  closelog();

  return 0;
}

